﻿/*
 * This file is part of CommunitySettlers.
 *
 * CommunitySettlers is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.

 * CommunitySettlers is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with CommunitySettlers.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Author: Christoph Husse
 * 
 * Also checkout our homepage: http://communitysettlers.codeplex.com/
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
using SettlersEngine;

namespace GameHostService
{
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode=ConcurrencyMode.Multiple,
        AutomaticSessionShutdown=true)] 
    public class GameHostService : IGameHostService
    {
        private Map<Guid, PlayerSession> m_Sessions = new Map<Guid, PlayerSession>(MapPolicy.None, (a, b) => a.CompareTo(b));
        private Map<Guid, GameSession> m_Games = new Map<Guid, GameSession>(MapPolicy.None, (a, b) => a.CompareTo(b));

        public Guid RequestSessionID(String inUsername, byte[] inPassword)
        {
            PlayerSession session;

            lock (m_Sessions)
            {
                do
                {
                    session = new PlayerSession();
                } while (m_Sessions.ContainsKey(session.SessionID));

                m_Sessions.Add(session.SessionID, session);
            }

            return session.SessionID;
        }

        public void CloseSession(Guid inSessionID)
        {
            PlayerSession session;

            lock (m_Sessions)
            {
                session = m_Sessions[inSessionID];

                m_Sessions.Remove(inSessionID);

                if (session.Game != null)
                    m_Games.Remove(session.Game.GameID);
            }

            session.Dispose();
        }

        private PlayerSession OpenSession(Guid inSessionID)
        {
            lock (m_Sessions)
            {
                PlayerSession session = m_Sessions[inSessionID];

                session.Touch();

                return session;
            }
        }

        public GameSetup QueryGameSetup(Guid inGameID)
        {
            return OpenGame(inGameID).Setup;
        }

        public Guid BeginGameHosting(Guid inSessionID, GameSetup inSetup)
        {
            PlayerSession session = OpenSession(inSessionID);

            inSetup.Validate();

            lock (m_Games)
            {
                if (session.Game != null)
                    throw new InvalidOperationException("Session is already associated with a game!");

                GameSession game;

                do
                {
                    game = new GameSession(inSetup);
                } while (m_Games.ContainsKey(game.GameID));

                m_Games.Add(game.GameID, game);

                session.Game = game;
            }

            session.Game.JoinPlayer(session);

            return session.Game.GameID;
        }

        private GameSession OpenGame(Guid inGameID)
        {
            lock (m_Games)
            {
                return m_Games[inGameID];
            }
        }

        public void JoinHostedGame(Guid inSessionID, Guid inGameID)
        {
            PlayerSession session = OpenSession(inSessionID);

            if (session.Game != null)
                throw new InvalidOperationException("Session is already associated with a game!");

            OpenGame(inGameID).JoinPlayer(session);
        }

        public byte[] SyncWithServer(Guid inSessionID, byte[] inData, out TimeSpan outServerTime, out List<GameHostEvent> outEvents)
        {
            PlayerSession session = OpenSession(inSessionID);

            if (session.Game == null)
                throw new InvalidOperationException("Session is not associated with a game!");

            session.Game.Broadcast(session, inData);

            return session.Game.FetchPacket(session, out outServerTime, out outEvents);
        }

        public void Ping() { }

        public void PingSession(Guid inSessionID)
        {
            OpenSession(inSessionID);
        }
    }
}
